package com.bestcem.xm.deliver.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bestcem.xm.component.mybatis.service.impl.XmServiceImpl;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.deliver.entity.ServiceSequence;
import com.bestcem.xm.deliver.mapper.ServiceSequenceMapper;
import com.bestcem.xm.deliver.service.ServiceSequenceService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Slf4j
@RequiredArgsConstructor
@Service
public class ServiceSequenceServiceImpl extends XmServiceImpl<ServiceSequenceMapper, ServiceSequence> implements ServiceSequenceService {

    private static final String SEQ_LEFT_PREFIX = "_SEQleft_";

    private static final String SEQ_PREFIX = "_SEQ_";

    private @NonNull RedisService redisService;

    @Override
    public Integer getSequence(String code, long incrBy) {
        // 判断步长
        if (incrBy <= 0) {
            return -1;
        }

        String leftKey = SEQ_LEFT_PREFIX + code;
        String seqKey = SEQ_PREFIX + code;

        RLock lock = redisService.getLock(code);
        try {
            lock.lock();
            // 查询还未放出的序号的数量
            long leftKeySeq = redisService.decr(leftKey, incrBy);
            boolean dbIncr = false;
            if (leftKeySeq < 0) {
                // leftKey已经减到0以下或者之前不存在, 不存在的时候可能需要创建记录
                dbIncr = getDbSequence(code, -1 * leftKeySeq);
            }

            // 直接从redis中获取seq自增后的结果
            int seq = redisService.incr(seqKey, incrBy).intValue();
            if (seq == incrBy) {
                // 第一次或key丢失
                // 从db中获取该序号的信息
                ServiceSequence sequence = findOneByCode(code);
                if (sequence.getSeq() == sequence.getIncrStep() - leftKeySeq) {
                    // 若db里的序号 == 设置的自增 - 剩余的还未释放的序号的数量
                    // 数据库的序号自增 + 1
                    findOneAndUpdateByCodeAndSeqInc(code, 1);
                } else {
                    // 缓存丢失
                    int lastDbSeq;
                    if (dbIncr) {
                        lastDbSeq = sequence.getSeq() - sequence.getIncrStep();
                    } else {
                        findOneAndUpdateByCodeAndSeqInc(code, sequence.getIncrStep());
                        lastDbSeq = sequence.getSeq();
                    }
                    int incrSeq = lastDbSeq - seq;
                    seq = redisService.incr(seqKey, incrSeq).intValue();
                    if (seq != incrSeq) {
                        findOneAndUpdateByCodeAndSeqInc(code, seq - lastDbSeq);
                    }
                }
            }
            return seq;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public Integer getCurrentSequence(String code) {
        code = SEQ_PREFIX + code;
        Integer sequence = (Integer) redisService.get(code);
        if (Objects.isNull(sequence)) {
            //缓存不存在则查库
            ServiceSequence sequenceRes = this.findOneByCode(code);
            if (Objects.nonNull(sequenceRes)) {
                sequence = sequenceRes.getSeq();
                //设置缓存
                redisService.set(code, sequence);
            }
        }
        return sequence;
    }

    private Boolean getDbSequence(String code, long incrBy) {
        ServiceSequence sequence = findOneByCode(code);
        if (ObjectUtil.isEmpty(sequence) || sequence.getSeq() < 0) {
            //创建一个新的记录
            sequence = createSequence(code);
        }
        Integer seq = sequence.getSeq();
        int incrStep = sequence.getIncrStep();
        incrStep += incrBy;
        String leftKey = SEQ_LEFT_PREFIX + code;
        // 根据 code 与 seq 更新 seq
        findOneAndUpdateByCodeAndSeq(code, seq, incrStep);
        // 更新redis
        redisService.incr(leftKey, incrStep);
        return true;
    }

    private ServiceSequence findOneByCode(String code) {
        LambdaQueryWrapper<ServiceSequence> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ServiceSequence::getCode, code).last("limit 1");
        // 从db中获取该序号的信息
        return baseMapper.selectOne(wrapper);
    }

    private ServiceSequence createSequence(String code) {
        ServiceSequence sequence = new ServiceSequence();
        sequence.setCode(code);
        sequence.setIncrStep(1000);
        sequence.setSeq(0);
        baseMapper.insert(sequence);
        return sequence;
    }

    private void findOneAndUpdateByCodeAndSeqInc(String code, int incr) {
        LambdaQueryWrapper<ServiceSequence> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ServiceSequence::getCode, code).last("limit 1");
        // 根据 code获取序号
        ServiceSequence serviceSequence = baseMapper.selectOne(wrapper);
        // 更新序号
        ServiceSequence updateSequence = new ServiceSequence();
        updateSequence.setSeq(serviceSequence.getSeq() + incr);
        updateSequence.setId(serviceSequence.getId());
        // 更新
        baseMapper.updateById(updateSequence);
    }

    private void findOneAndUpdateByCodeAndSeq(String code, Integer seq, Integer incrStep) {
        // 根据 code获取序号
        ServiceSequence serviceSequence = findOneByCode(code);
        // 更新序号
        ServiceSequence updateSequence = new ServiceSequence();
        updateSequence.setSeq(incrStep);
        updateSequence.setId(serviceSequence.getId());
        // 更新
        baseMapper.updateById(updateSequence);
    }
}
